/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2006-2011 ScriptDev2 <http://www.scriptdev2.com/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "ScriptPCH.h"
#include "naxxramas.h"

const DoorData doorData[] = { { 181126, BOSS_ANUBREKHAN, DOOR_TYPE_ROOM,
		BOUNDARY_S }, { 181195, BOSS_ANUBREKHAN, DOOR_TYPE_PASSAGE, 0 }, {
		194022, BOSS_FAERLINA, DOOR_TYPE_PASSAGE, 0 }, { 181209, BOSS_FAERLINA,
		DOOR_TYPE_PASSAGE, 0 }, { 181209, BOSS_MAEXXNA, DOOR_TYPE_ROOM,
		BOUNDARY_SW }, { 181200, BOSS_NOTH, DOOR_TYPE_ROOM, BOUNDARY_N }, {
		181201, BOSS_NOTH, DOOR_TYPE_PASSAGE, BOUNDARY_E }, { 181202, BOSS_NOTH,
		DOOR_TYPE_PASSAGE, 0 }, { 181202, BOSS_HEIGAN, DOOR_TYPE_ROOM,
		BOUNDARY_N }, { 181203, BOSS_HEIGAN, DOOR_TYPE_PASSAGE, BOUNDARY_E }, {
		181241, BOSS_HEIGAN, DOOR_TYPE_PASSAGE, 0 }, { 181241, BOSS_LOATHEB,
		DOOR_TYPE_ROOM, BOUNDARY_W }, { 181123, BOSS_PATCHWERK,
		DOOR_TYPE_PASSAGE, 0 }, { 181123, BOSS_GROBBULUS, DOOR_TYPE_ROOM, 0 }, {
		181120, BOSS_GLUTH, DOOR_TYPE_PASSAGE, BOUNDARY_NW }, { 181121,
		BOSS_GLUTH, DOOR_TYPE_PASSAGE, 0 }, { 181121, BOSS_THADDIUS,
		DOOR_TYPE_ROOM, 0 }, { 181124, BOSS_RAZUVIOUS, DOOR_TYPE_PASSAGE, 0 }, {
		181124, BOSS_GOTHIK, DOOR_TYPE_ROOM, BOUNDARY_N }, { 181125,
		BOSS_GOTHIK, DOOR_TYPE_PASSAGE, BOUNDARY_S }, { 181119, BOSS_GOTHIK,
		DOOR_TYPE_PASSAGE, 0 }, { 181119, BOSS_HORSEMEN, DOOR_TYPE_ROOM,
		BOUNDARY_NE },
		{ 181225, BOSS_SAPPHIRON, DOOR_TYPE_PASSAGE, BOUNDARY_W }, { 181228,
				BOSS_KELTHUZAD, DOOR_TYPE_ROOM, BOUNDARY_S }, { 0, 0,
				DOOR_TYPE_ROOM, 0 }, // EOF
		};

const MinionData minionData[] = {
//{16573,     BOSS_ANUBREKHAN},     there is no spawn point in db, so we do not add them here
		{ 16506, BOSS_FAERLINA }, { 16803, BOSS_RAZUVIOUS }, { 16063,
				BOSS_HORSEMEN }, { 16064, BOSS_HORSEMEN }, { 16065,
				BOSS_HORSEMEN }, { 30549, BOSS_HORSEMEN }, { 0, 0, } };

enum eEnums {
	GO_HORSEMEN_CHEST_HERO = 193426, GO_HORSEMEN_CHEST = 181366, //four horsemen event, DoRespawnGameObject() when event == DONE
	GO_GOTHIK_GATE = 181170,
	GO_KELTHUZAD_PORTAL01 = 181402,
	GO_KELTHUZAD_PORTAL02 = 181403,
	GO_KELTHUZAD_PORTAL03 = 181404,
	GO_KELTHUZAD_PORTAL04 = 181405,
	GO_KELTHUZAD_TRIGGER = 181444,

	SPELL_ERUPTION = 29371
};

const float HeiganPos[2] = { 2796, -3707 };
const float HeiganEruptionSlope[3] = { (-3685 - HeiganPos[1])
		/ (2724 - HeiganPos[0]), (-3647 - HeiganPos[1]) / (2749 - HeiganPos[0]),
		(-3637 - HeiganPos[1]) / (2771 - HeiganPos[0]), };

// 0  H      x
//  1        ^
//   2       |
//    3  y<--o
inline uint32 GetEruptionSection(float x, float y) {
	y -= HeiganPos[1];
	if (y < 1.0f)
		return 0;

	x -= HeiganPos[0];
	if (x > -1.0f)
		return 3;

	float slope = y / x;
	for (uint32 i = 0; i < 3; ++i)
		if (slope > HeiganEruptionSlope[i])
			return i;
	return 3;
}

class instance_naxxramas: public InstanceMapScript {
public:
	instance_naxxramas() :
			InstanceMapScript("instance_naxxramas", 533) {
	}

	InstanceScript* GetInstanceScript(InstanceMap* pMap) const {
		return new instance_naxxramas_InstanceMapScript(pMap);
	}

	struct instance_naxxramas_InstanceMapScript: public InstanceScript {
		instance_naxxramas_InstanceMapScript(Map* pMap) :
				InstanceScript(pMap) {
			SetBossNumber(MAX_BOSS_NUMBER);
			LoadDoorData(doorData);
			LoadMinionData(minionData);
		}

		std::set<uint64> HeiganEruptionGUID[4];
		uint64 GothikGateGUID;
		uint64 HorsemenChestGUID;
		uint64 SapphironGUID;
		uint64 uiFaerlina;
		uint64 uiThane;
		uint64 uiLady;
		uint64 uiBaron;
		uint64 uiSir;

		uint64 uiThaddius;
		uint64 uiFeugen;
		uint64 uiStalagg;

		uint64 uiKelthuzad;
		uint64 uiKelthuzadTrigger;
		uint64 uiPortals[4];

		GOState gothikDoorState;

		time_t minHorsemenDiedTime;
		time_t maxHorsemenDiedTime;

		void OnCreatureCreate(Creature* pCreature, bool add) {
			switch (pCreature->GetEntry()) {
			case 15989:
				SapphironGUID = add ? pCreature->GetGUID() : 0;
				return;
			case 15953:
				uiFaerlina = pCreature->GetGUID();
				return;
			case 16064:
				uiThane = pCreature->GetGUID();
				return;
			case 16065:
				uiLady = pCreature->GetGUID();
				return;
			case 30549:
				uiBaron = pCreature->GetGUID();
				return;
			case 16063:
				uiSir = pCreature->GetGUID();
				return;
			case 15928:
				uiThaddius = pCreature->GetGUID();
				return;
			case 15930:
				uiFeugen = pCreature->GetGUID();
				return;
			case 15929:
				uiStalagg = pCreature->GetGUID();
				return;
			case 15990:
				uiKelthuzad = pCreature->GetGUID();
				return;
			}

			AddMinion(pCreature, add);
		}

		void OnGameObjectCreate(GameObject* pGo, bool add) {
			if (pGo->GetGOInfo()->displayId == 6785
					|| pGo->GetGOInfo()->displayId == 1287) {
				uint32 section = GetEruptionSection(pGo->GetPositionX(),
						pGo->GetPositionY());
				if (add)
					HeiganEruptionGUID[section].insert(pGo->GetGUID());
				else
					HeiganEruptionGUID[section].erase(pGo->GetGUID());
				return;
			}

			switch (pGo->GetEntry()) {
			case GO_BIRTH:
				if (!add && SapphironGUID) {
					if (Creature *pSapphiron = instance->GetCreature(SapphironGUID))
						pSapphiron->AI()->DoAction(DATA_SAPPHIRON_BIRTH);
					return;
				}
			case GO_GOTHIK_GATE:
				GothikGateGUID = add ? pGo->GetGUID() : 0;
				pGo->SetGoState(gothikDoorState);
				break;
			case GO_HORSEMEN_CHEST:
				HorsemenChestGUID = add ? pGo->GetGUID() : 0;
				break;
			case GO_HORSEMEN_CHEST_HERO:
				HorsemenChestGUID = add ? pGo->GetGUID() : 0;
				break;
			case GO_KELTHUZAD_PORTAL01:
				uiPortals[0] = pGo->GetGUID();
				break;
			case GO_KELTHUZAD_PORTAL02:
				uiPortals[1] = pGo->GetGUID();
				break;
			case GO_KELTHUZAD_PORTAL03:
				uiPortals[2] = pGo->GetGUID();
				break;
			case GO_KELTHUZAD_PORTAL04:
				uiPortals[3] = pGo->GetGUID();
				break;
			case GO_KELTHUZAD_TRIGGER:
				uiKelthuzadTrigger = pGo->GetGUID();
				break;
			}

			AddDoor(pGo, add);
		}

		void SetData(uint32 id, uint32 value) {
			switch (id) {
			case DATA_HEIGAN_ERUPT:
				HeiganErupt(value);
				break;
			case DATA_GOTHIK_GATE:
				if (GameObject *pGothikGate = instance->GetGameObject(GothikGateGUID))
					pGothikGate->SetGoState(GOState(value));
				gothikDoorState = GOState(value);
				break;

			case DATA_HORSEMEN0:
			case DATA_HORSEMEN1:
			case DATA_HORSEMEN2:
			case DATA_HORSEMEN3:
				if (value == NOT_STARTED) {
					minHorsemenDiedTime = 0;
					maxHorsemenDiedTime = 0;
				} else if (value == DONE) {
					time_t now = time(NULL);

					if (minHorsemenDiedTime == 0)
						minHorsemenDiedTime = now;

					maxHorsemenDiedTime = now;
				}
				break;
			}
		}

		uint64 GetData64(uint32 id) {
			switch (id) {
			case DATA_FAERLINA:
				return uiFaerlina;
			case DATA_THANE:
				return uiThane;
			case DATA_LADY:
				return uiLady;
			case DATA_BARON:
				return uiBaron;
			case DATA_SIR:
				return uiSir;
			case DATA_THADDIUS:
				return uiThaddius;
			case DATA_FEUGEN:
				return uiFeugen;
			case DATA_STALAGG:
				return uiStalagg;
			case DATA_KELTHUZAD:
				return uiKelthuzad;
			case DATA_KELTHUZAD_PORTAL01:
				return uiPortals[0];
			case DATA_KELTHUZAD_PORTAL02:
				return uiPortals[1];
			case DATA_KELTHUZAD_PORTAL03:
				return uiPortals[2];
			case DATA_KELTHUZAD_PORTAL04:
				return uiPortals[3];
			case DATA_KELTHUZAD_TRIGGER:
				return uiKelthuzadTrigger;
			}
			return 0;
		}

		bool SetBossState(uint32 id, EncounterState state) {
			if (!InstanceScript::SetBossState(id, state))
				return false;

			if (id == BOSS_HORSEMEN && state == DONE) {
				if (GameObject *pHorsemenChest = instance->GetGameObject(HorsemenChestGUID))
					pHorsemenChest->SetRespawnTime(
							pHorsemenChest->GetRespawnDelay());
			}

			return true;
		}

		void HeiganErupt(uint32 section) {
			for (uint32 i = 0; i < 4; ++i) {
				if (i == section)
					continue;

				for (std::set<uint64>::const_iterator itr =
						HeiganEruptionGUID[i].begin();
						itr != HeiganEruptionGUID[i].end(); ++itr) {
					if (GameObject *pHeiganEruption = instance->GetGameObject(*itr)) {
						pHeiganEruption->SendCustomAnim(
								pHeiganEruption->GetGoAnimProgress());
						pHeiganEruption->CastSpell(NULL, SPELL_ERUPTION);
					}
				}
			}
		}

		bool CheckAchievementCriteriaMeet(uint32 criteria_id,
				Player const* /*source*/, Unit const* /*target = NULL*/,
				uint32 /*miscvalue1 = 0*/) {
			switch (criteria_id) {
			case 7600: // Criteria for achievement 2176: And They Would All Go Down Together 15sec of each other 10-man
				if (Difficulty(instance->GetSpawnMode())
						== RAID_DIFFICULTY_10MAN_NORMAL
						&& (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
					return true;
				return false;
			case 7601: // Criteria for achievement 2177: And They Would All Go Down Together 15sec of each other 25-man
				if (Difficulty(instance->GetSpawnMode())
						== RAID_DIFFICULTY_25MAN_NORMAL
						&& (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
					return true;
				return false;
			case 13233: // Criteria for achievement 2186: The Immortal (25-man)
				// TODO.
				break;
			case 13237: // Criteria for achievement 2187: The Undying (10-man)
				// TODO.
				break;
			}
			return false;
		}

		std::string GetSaveData() {
			std::ostringstream saveStream;
			saveStream << GetBossSaveData() << " " << gothikDoorState;
			return saveStream.str();
		}

		void Load(const char * data) {
			std::istringstream loadStream(LoadBossState(data));
			uint32 buff;
			loadStream >> buff;
			gothikDoorState = GOState(buff);
		}
	};
};

void AddSC_instance_naxxramas() {
	new instance_naxxramas();
}
